package service

import (
	"context"
	"github.com/ecodeclub/ekit/queue"
	"github.com/ecodeclub/ekit/slice"
	commentv1 "jk-time/webook/api/proto/gen/comment/v1"
	"jk-time/webook/internal/domain"
	"jk-time/webook/internal/repository"
	"math"
	"time"
)

//go:generate mockgen -source=./ranking.go -package=mocks -destination=mocks/ranking.mock.go RankingService
type RankingService interface {
	TopN(ctx context.Context) error
	TopN1(ctx context.Context) ([]domain.PublishedArticle, error)
}
type BatchRankingService struct {
	artSvc        ArticleService
	insvc         InteractiveService
	bitchSize     int
	n             int
	scoreFunc     func(uTime time.Time, likeCnt int64) float64
	repo          repository.RankingRepository
	commentClient commentv1.CommentSerivceClient
}

func NewBatchRankingService(artSvc ArticleService, insvc InteractiveService, repo repository.RankingRepository,
	commentClient commentv1.CommentSerivceClient) RankingService {
	return &BatchRankingService{
		artSvc:    artSvc,
		insvc:     insvc,
		bitchSize: 10,
		n:         10,
		scoreFunc: func(utime time.Time, likeCnt int64) float64 {
			return float64(float64(likeCnt-1) / math.Pow(time.Since(utime).Hours()+2, 1.5))
		},
		repo:          repo,
		commentClient: commentClient,
	}
}
func (b *BatchRankingService) TopN(ctx context.Context) error {
	//数据库
	arts, err := b.TopN1(ctx)
	if err != nil {
		return err
	}
	pac := make([]domain.PublishedArticleComment, len(arts))
	for i, art := range arts {
		resp, err := b.commentClient.GetCommentList(ctx, &commentv1.CommentListRequest{
			Biz:   "article",
			BizId: art.Id,
			MinId: 300,
		})
		if err != nil {
			// 打印错误
			continue
		}
		pac[i].Puh = art
		pac[i].Comment = commentRespToDomain(resp.Comments)
	}
	//redis
	return b.repo.SetTopNAndComment(ctx, pac)
	//return b.repo.SetTopN(ctx, arts)
}
func (b *BatchRankingService) TopN1(ctx context.Context) ([]domain.PublishedArticle, error) {
	now := time.Now()
	// 只计算七天内的，因为超过七天的我们可以认为绝对不可能成为热榜了
	// 如果一个批次里面 utime 最小已经是七天之前的，我们就中断当前计算
	ddl := now.Add(-time.Hour * 24 * 7)
	offset := 0
	type Score struct {
		art   domain.PublishedArticle
		score float64
	}
	//非并发安全
	que := queue.NewPriorityQueue[Score](b.n, func(src Score, dst Score) int {
		if src.score > dst.score {
			return 1
		} else if src.score == dst.score {
			return 0
		} else {
			return -1
		}
	})
	for {
		//这里拿了一批已发表
		arts, err := b.artSvc.ListPub(ctx, now, offset, b.bitchSize)
		if err != nil {
			return nil, err
		}
		if len(arts) == 0 {
			break
		}
		//提取id
		artIds := slice.Map[domain.PublishedArticle, int64](arts, func(idx int, src domain.PublishedArticle) int64 {
			return src.Id
		})
		//id要去找到对应的点赞数据，返回map[id]data
		intrMap, err := b.insvc.GetByIds(ctx, "article", artIds)
		if err != nil {
			return nil, err
		}
		for _, art := range arts {
			intr := intrMap[art.Id]
			//计算对应的分数
			score := b.scoreFunc(art.Utime, intr.LikeCnt)
			ele := Score{art: art, score: score}
			err = que.Enqueue(ele)
			//容量满了
			if err == queue.ErrOutOfCapacity {
				min, _ := que.Peek()
				if score > min.score {
					_, _ = que.Dequeue()
					err = que.Enqueue(ele)
				}
			}
		}
		offset = offset + len(arts)
		// 降序排列，所以求最后的
		if len(arts) < b.bitchSize || arts[len(arts)-1].Utime.Before(ddl) {
			//未达标，没有下一批
			break
		}
	}

	//最后的结果
	ql := que.Len()
	res := make([]domain.PublishedArticle, ql)
	for i := ql - 1; i >= 0; i-- {
		val, _ := que.Dequeue()
		res[i] = val.art
	}
	return res, nil
}
func commentRespToDomain(comments []*commentv1.Comment) []domain.Comment {
	res := make([]domain.Comment, len(comments))
	for i, c := range comments {
		comment := domain.Comment{
			Id: c.Id,
			Commentator: domain.Commentator{
				ID: c.Uid,
			},
			Biz:     c.Biz,
			BizId:   c.Bizid,
			Content: c.Content,
			CTime:   c.Ctime.AsTime(),
		}
		if c.ParentComment != nil {
			comment.ParentComment = &domain.Comment{
				Id: c.ParentComment.Id,
			}
		}
		if c.RootComment != nil {
			comment.RootComment = &domain.Comment{
				Id: c.RootComment.Id,
			}
		}
		res[i] = comment
	}
	return res
}
